/** 
Copyright (c) 2010, Sheikh Abdul Wahid Ahmed
Details @ http://extremecodeworld.codeplex.com/license
**/

using System;
using System.IO;
using System.Collections;
using System.Xml;
using System.Reflection;
using System.Diagnostics;

namespace EXtremecode.Logging
{
	/// <summary>
	/// Summary description for Logger.
	/// </summary>
	public class Logger
	{
		private static StreamWriter s_writer;
		private static string s_strCurrentFileName;
		private const string FILE_NAME_FORMAT="yyyy-MM-dd";
		private const string TIME_FORMAT="hh:mm:ss tt";
		private const string ASSEMBLY_NAME="EXtremecode";
		private static int logStatus = int.MinValue;
        private static long counter = 0;

		public static void Write()
		{
			if (LogStatus < LoggingStatus.INFO) return; 
			Write(null,null,null);
		}
		public static void Write(string message)
		{
			if (LogStatus < LoggingStatus.INFO) return;
			Write(null,message,null);
		}
		public static void Write(string message,string tag)
		{
			if (LogStatus < LoggingStatus.INFO) return;
			Write(null,message,tag);
		}
		public static Exception Write(Exception ex)
		{
			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex,null,null);
		}
		public static Exception Write(Exception ex,string tag)
		{
			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex,null,tag);
		}
		public static Exception Write(string message,Exception ex)
		{
			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex,message,null);
		}
		

		public static void Debug()
		{
#if !DEBUG
            return;
#endif
            
            
			if (LogStatus < LoggingStatus.INFO) return;
			Write(null, null, null);
            
		}
		public static void Debug(string message)
		{
#if !DEBUG
            return;
#endif
            
            
			if (LogStatus < LoggingStatus.INFO) return;
			Write(null, message, null);

		}
		public static void Debug(string message, string tag)
		{
#if !DEBUG
            return;
#endif
            
            
			if (LogStatus < LoggingStatus.INFO) return;
			Write(null, message, tag);
		}
		public static Exception Debug(Exception ex)
		{
#if !DEBUG
            return ex;
#endif
            
            
			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex, null, null);
		}
		public static Exception Debug(Exception ex, string tag)
		{
#if !DEBUG
            return ex;
#endif


			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex, null, tag);
		}
		public static Exception Debug(string message, Exception ex)
		{
#if !DEBUG
            return ex;
#endif


			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex, message, null);
		}
        
		public static Exception Debug(Exception ex, string message, string tag)
		{
#if !DEBUG
            return ex;
#endif

			if (LogStatus < LoggingStatus.ERROR) return ex;
			return Write(ex, message, tag);
		}



		private static Exception Write(Exception ex,string message,string tag)
		{
            
            if (ex is LoggerException
                || LogStatus < LoggingStatus.ERROR)
            {
                //Error Allready Logged by child method so no need to logg it
                return ex;
            }
            MethodBase method = new StackFrame(2).GetMethod(); 
            string assemblyName = method.ReflectedType.Assembly.GetName().Name; 
			string className =  method.ReflectedType.Name;
            string methodName = method.ToString();
            			

			message=(message==null)? "": message;
			tag=(tag==null)? "": tag;
            string errorMessage=(ex==null)? "": ex.Message;
            string errorDetails = (ex == null) ? "" : ex.ToString(); 
			bool isError=(ex==null)? false: true;
            string css = assemblyName.Replace('.', '-')
                + ((tag == "")
                        ? ""
                        : "-" + tag);


			//Creating Xml String
			string strXml=CreateXml(
                 methodName
                ,assemblyName
                ,className
                ,message
                ,errorMessage
                ,errorDetails
                ,isError
                ,tag
                ,css);

			StreamWriter writer=GetWriter();
			lock(writer)
			{
                string endTag = "</logging>";
                
                Stream stream=writer.BaseStream;
				stream.SetLength(stream.Length-endTag.Length);
				stream.Position=stream.Length;
				writer.WriteLine(strXml);
				writer.Write(endTag);
				writer.Flush();

				//testing only
                //XmlTextWriter xtw=new XmlTextWriter(Console.Out);

			}
			if(ex != null)
				return new LoggerException(ex);
			else
				return null;
			
		}
		
		private static string CreateXml(string methodName
                ,string assemblyName
                ,string className
                ,string message
                ,string errorMessage
                ,string errorDetails
                ,bool isError
                ,string tag
                ,string css)
		{
			XmlDocument doc=new XmlDocument();
			XmlElement element= doc.CreateElement("log");
			XmlAttribute atb;
			//---Attributes---
			
			//loggingTime
			atb=doc.CreateAttribute("loggingTime");
			atb.InnerText=DateTime.Now.ToString(TIME_FORMAT);
			element.Attributes.Append(atb);
			
			//method
			atb=doc.CreateAttribute("method");
			atb.InnerText=methodName;
			element.Attributes.Append(atb);

            //className
            atb = doc.CreateAttribute("className");
            atb.InnerText = className;
            element.Attributes.Append(atb);
			
			//assembly
            atb = doc.CreateAttribute("assembly");
			atb.InnerText=assemblyName;
			element.Attributes.Append(atb);

			//message
			atb=doc.CreateAttribute("message");
			atb.InnerText=message;
			element.Attributes.Append(atb);

			//errorMessage
			atb=doc.CreateAttribute("errorMessage");
            atb.InnerText = errorMessage;
			element.Attributes.Append(atb);

            //errorDetails
            atb = doc.CreateAttribute("errorDetails");
            atb.InnerText = errorDetails;
            element.Attributes.Append(atb);

			//isError
			atb=doc.CreateAttribute("isError");
			atb.InnerText=isError.ToString().ToLower();
			element.Attributes.Append(atb);

            //tag
			atb=doc.CreateAttribute("tag");
			atb.InnerText=tag;
			element.Attributes.Append(atb);

            //css
            atb = doc.CreateAttribute("css");
            atb.InnerText = css;
            element.Attributes.Append(atb);

            //order
            atb = doc.CreateAttribute("order");
            atb.InnerText = string.Format("{0:000000000000}",++counter);
            element.Attributes.Append(atb);

			return element.OuterXml;
		}
		
		private static string GetLine(int lineNo,string[] lineArray)
		{
			return (lineArray == null || lineNo > lineArray.Length-1) ? "": lineArray[lineNo]; 
		}

        [System.Runtime.CompilerServices.MethodImpl
       (System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
        private static StreamWriter GetWriter()
        {
            if (s_writer == null
                || s_writer.BaseStream == null
                || s_strCurrentFileName != DateTime.Today.ToString(FILE_NAME_FORMAT))
            {
                FileStream stream = CreateFileStream();

                if (s_writer != null)
                {
                    //close current stream first
                    s_writer.Close();
                    s_writer.Dispose();
                }
                s_writer = new StreamWriter(stream);

            }

            //check file size 
            FileInfo fileInfo = new FileInfo(((FileStream)s_writer.BaseStream).Name);
            if (fileInfo.Length >= Configurations.Provider.LoggingMaxFileSize)
            {
                //close and rename current file and create new writer.
                s_writer.Close();
                s_writer.Dispose();
                fileInfo.MoveTo(
                    fileInfo.FullName.Substring(0, fileInfo.FullName.Length - 4) //remove extention
                    + " " + DateTime.Now.ToString("HH mm ss ffff").Replace(':', ' ') + ".xml");

                FileStream stream = CreateFileStream();
                s_writer = new StreamWriter(stream);
            }


            return s_writer;
        }
		private static FileStream CreateFileStream()
		{
			
			
			string strDirPath=Configurations.Provider.LoggingDirectoryPath;
			if(strDirPath==null)
				throw new Exception("Logging directory is not set");
			strDirPath = System.AppDomain.CurrentDomain.BaseDirectory + strDirPath; 
			DirectoryInfo dir;
			if(!Directory.Exists(strDirPath))
				dir=Directory.CreateDirectory(strDirPath);
			else
				dir=new DirectoryInfo(strDirPath);


			FileStream fileStream;
			s_strCurrentFileName=DateTime.Today.ToString(FILE_NAME_FORMAT);
			string strFileName=string.Format("{0}.xml",s_strCurrentFileName);
			FileInfo[] files = dir.GetFiles(strFileName);
			if(files.Length>0)
			{
				fileStream= files[0].Open(FileMode.Open,FileAccess.ReadWrite,FileShare.Read);
			}
			else
			{
				FileInfo file=new FileInfo(string.Format(@"{0}\{1}",dir.FullName,strFileName));
				fileStream=file.Open(FileMode.Create,FileAccess.ReadWrite,FileShare.Read);
				
				TextWriter writer=new StreamWriter(fileStream);
				writer.WriteLine("<?xml version='1.0'?><?xml-stylesheet type='text/xsl' href='Logging.xsl'?>");
				writer.WriteLine("<logging>");
				writer.Write("</logging>");
				writer.Flush();

			}
			return fileStream;

		}

		private static int LogStatus
		{
			get
			{
				if (logStatus == int.MinValue)
				{
					System.Configuration.AppSettingsReader reader = new System.Configuration.AppSettingsReader();
					logStatus  = LoggingStatus.INFO;
					try
					{
						string strCurrentStatus = (string)reader.GetValue("LoggingStatus", typeof(string));
						switch(strCurrentStatus)
						{
							case "Off":
								logStatus = LoggingStatus.OFF;
								break;
							case "Error":
								logStatus = LoggingStatus.ERROR;
								break;
							case "Info":
								logStatus = LoggingStatus.INFO;
								break;
							default: 
								logStatus = LoggingStatus.INFO;
								break;


						}
					}
					catch { }


				}
				return logStatus;
                
			}
		}


		class LoggingStatus
		{
			public const int OFF = 1;
			public const int ERROR= 2;
			public const int INFO= 3;
		}
		
	}
}
